﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.Services.Discovery;
using System.Web.Services.Protocols;
using System.Xml;
using System.Net;

namespace Vision.Core.Components
{
    public class DynamicWebService : IDisposable
    {
        /// <summary>
        /// 
        /// </summary>
        private Assembly ServiceAssembly { get; set; }

        /// <summary>
        /// 
        /// </summary>        
        private dynamic ServiceInstance { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public List<string> AdditionalReferences { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public ICredentials Credentials { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public HttpProtocol Protocol { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string WsdlUrl { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string TypeName { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public DynamicWebService()
        {
            this.Protocol = HttpProtocol.Soap;
            this.Credentials = null;
            this.AdditionalReferences = new List<string>();
        }

        /// <summary>
        /// 
        /// </summary>
        public void InitService()
        {
            ServiceDescriptionImporter sdImporter = new ServiceDescriptionImporter();

            sdImporter.ProtocolName = this.Protocol.ToString();
            sdImporter.Style = ServiceDescriptionImportStyle.Client;
            sdImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;

            using (XmlTextReader xmlreader = new XmlTextReader(this.WsdlUrl)){
                if (!ServiceDescription.CanRead(xmlreader)){
                    throw new Exception("Invalid Web Service Description");
                }
                sdImporter.AddServiceDescription(ServiceDescription.Read(xmlreader), null, null);
            }

            this.ServiceAssembly = this.CompileAssembly(sdImporter);
            this.ServiceInstance = this.ServiceAssembly.CreateInstance(this.GetTypeName());

            if (this.Credentials != null){
                this.ServiceInstance.Credentials = this.Credentials;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="methodName"></param>
        /// <param name="methodParams"></param>
        /// <returns></returns>
        public T InvokeMethod<T>(string methodName, List<object> methodParams)
        {
            MethodInfo mi = this.ServiceInstance.GetType().GetMethod(methodName);
            return mi.Invoke(this.ServiceInstance, methodParams.ToArray());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<Type> GetServiceTypes()
        {
            return new List<Type>(this.ServiceAssembly.GetTypes());
        }

        /// <summary>
        /// 
        /// </summary>
        public List<MethodInfo> GetServiceMethods()
        {
            return new List<MethodInfo>(this.ServiceInstance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="descriptionImporter"></param>
        /// <returns></returns>
        private Assembly CompileAssembly(ServiceDescriptionImporter sdImporter)
        {
            CodeNamespace codeNamespace = new CodeNamespace();
            CodeCompileUnit codeUnit = new CodeCompileUnit();

            codeUnit.Namespaces.Add(codeNamespace);

            ServiceDescriptionImportWarnings importWarnings = sdImporter.Import(codeNamespace, codeUnit);

            if (importWarnings > 0){
                throw new Exception("Invalid WSDL");
            }

            CompilerParameters parameters = new CompilerParameters();

            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add("System.Xml.dll");
            parameters.ReferencedAssemblies.Add("System.Web.Services.dll");
            parameters.ReferencedAssemblies.Add("System.Data.dll");

            foreach (string ar in this.AdditionalReferences){
                if (!parameters.ReferencedAssemblies.Contains(ar)){
                    parameters.ReferencedAssemblies.Add(ar);
                }
            }

            parameters.GenerateInMemory = true;
            parameters.GenerateExecutable = false;
            parameters.IncludeDebugInformation = false;

            CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");
            CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit);

            foreach (CompilerError oops in results.Errors){                
                throw new Exception("Compilation Error Creating Assembly");
            }

            return results.CompiledAssembly;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string GetTypeName()
        {
            string typeName = this.TypeName;
            if (typeName == null){
                foreach (Type ty in this.ServiceAssembly.GetTypes()){
                    if (ty.BaseType == typeof(SoapHttpClientProtocol)){
                        typeName = ty.Name;
                        break;
                    }
                }
            }
            return typeName;
        }

        /// <summary>
        /// 
        /// </summary>
        public enum HttpProtocol
        {
            Soap,
            Soap12,
            HttpPost,
            HttpGet,
        }

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            this.ServiceAssembly = null;
            this.ServiceInstance = null;
        }
    }    
}
